#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @FileName  :utils.py
# @Time      :2025/5/22 09:31
# @Author    :雨霓同学
# @Function  :
import os
import shutil
import argparse
import torch
import logging
from datetime import datetime
import platform
from pathlib import Path
from .paths import CONFIGS_DIR, CHECKPOINTS_DIR,RUNS_DIR, PRETRAINED_MODELS_DIR,YOLO_SERVICE_DIR  # 相对导入
from .configs import DEFAULT_TRAIN_CONFIG, DEFAULT_INFER_CONFIG, DEFAULT_VAL_CONFIG, COMMENTED_TRAIN_CONFIG, COMMENTED_INFER_CONFIG, COMMENTED_VAL_CONFIG  #, COMMENTED_CONFIG
from .dataset import get_dataset_info
from functools import wraps
import time
import yaml

VALID_YOLO_TRAIN_ARGS = set(DEFAULT_TRAIN_CONFIG.keys())
VALID_YOLO_VAL_ARGS = set(DEFAULT_VAL_CONFIG.keys())
VALID_YOLO_INFER_ARGS = set(DEFAULT_INFER_CONFIG.keys())

logger = logging.getLogger(__name__)


def setup_logging(base_path, log_type="train", model_name=None, encoding='utf-8-sig', log_level=logging.INFO, temp_log=False):
    """
    配置日志，保存到 logging/{log_type} 子目录，支持临时文件名。

    功能概述：
    - 初始化 Python 的 logging 模块，创建日志记录器，用于记录训练、推理或其他过程的日志信息。
    - 日志文件保存到指定目录（base_path/logging/log_type），文件名包含前缀（临时或类型）、时间戳和可选的模型名称。
    - 支持临时文件名（temp_时间戳_模型名.log），用于训练场景中训练目录名（如 train2）未知的情况，训练后可通过 rename_log_file 重命名。
    - 支持同时输出到文件和控制台，便于实时查看和持久化存储。
    - 记录日志元数据（目录、文件名、编码、类型、级别），便于调试和追踪。

    参数说明：
    :param base_path (Path): 项目根目录（Path 对象，用于构建日志存储路径。
    :param log_type (str): 日志类型，默认为 "train"，用于子目录名和文件名（如 train、infer），反映日志的用途。
    :param model_name (str, optional): 模型名称（例如 yolov8n），用于日志文件名，默认为 None（不包含模型名）。
    :param encoding (str): 日志文件编码，默认为 'utf-8-sig'（带 BOM 的 UTF-8，适合 Windows 环境），确保日志文件正确显示中文。
    :param log_level (int): 日志级别，默认为 logging.INFO，控制日志的详细程度（例如 DEBUG、INFO、ERROR）。
    :param temp_log (bool): 是否使用临时文件名，默认为 False。若为 True，使用 "temp" 作为文件名前缀，训练后可重命名（例如 train2_时间戳_模型名.log）。

    返回值：
    :return: logging.Logger: 配置好的日志记录器，包含文件和控制台处理器，可直接使用 logger.info() 等记录日志。

    处理逻辑：
    1. 创建日志目录（base_path/logging/log_type），确保目录存在。
    2. 生成时间戳（YYYYMMDD_HHMMSS），用于文件名唯一性。
    3. 根据 temp_log 参数选择前缀（temp 或 log_type），构造日志文件名。
    4. 初始化日志记录器，设置日志级别。
    5. 添加文件处理器（写入日志文件）和控制台处理器（输出到终端）。
    6. 记录日志元数据（目录、文件名、编码等）。
    7. 返回配置好的日志记录器。

    注意事项：
    - 日志目录基于 base_path 和 log_type，需确保 base_path 可写。
    - 使用 utf-8-sig 编码以支持 Windows 下的中文日志，必要时可调整为 utf-8。
    - logger.handlers 检查避免重复添加处理器，确保同一 logger 不会重复输出。
    - 时间戳格式为 %Y%m%d_%H%M%S（例如 20250522_160405），保证文件名唯一。
    - temp_log=True 时，文件名以 "temp" 开头，需配合 rename_log_file 使用，以匹配训练目录名（如 train2）。

    """
    # 构建日志存储目录，格式为 base_path/logging/log_type
    # 例如：C:\Users\Matri\Desktop\Safe\yoloserver\logging\train
    # parents=True 确保父目录（如 base_path/logging）存在，exist_ok=True 避免目录已存在时抛出错误
    log_dir = base_path / "logging" / log_type
    log_dir.mkdir(parents=True, exist_ok=True)

    # 生成当前时间戳，格式为 YYYYMMDD_HHMMSS（例如 20250522_160405）
    # 用于日志文件名，确保每次运行生成唯一的文件名，避免文件冲突
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")

    # 根据 temp_log 参数选择文件名前缀
    # temp_log=True 时，使用 "temp"（例如 temp_20250522_160405_yolov8n.log）
    # temp_log=False 时，使用 log_type（例如 train_20250522_160405_yolov8n.log）
    prefix = "temp" if temp_log else log_type

    # 构造日志文件名，格式为 {prefix}_{timestamp}_{model_name}.log 或 {prefix}_{timestamp}.log
    # 例如：temp_20250522_160405_yolov8n.log 或 train_20250522_160405_yolov8n.log
    log_filename = f"{prefix}_{timestamp}_{model_name}.log" if model_name else f"{prefix}_{timestamp}.log"

    # 构建日志文件的完整路径
    # 例如：C:\Users\Matri\Desktop\Safe\yoloserver\logging\train\temp_20250522_160405_yolov8n.log
    log_file = log_dir / log_filename

    # 获取或创建日志记录器，名称为 "YOLO_Training"，用于整个项目的日志记录
    # 使用单一名称确保所有模块共享同一个 logger，避免重复输出
    logger = logging.getLogger("YOLO_Training")

    # 设置日志级别（例如 logging.INFO），控制哪些级别的日志被记录
    # 例如：INFO 级别会记录 INFO、WARNING、ERROR，但忽略 DEBUG
    logger.setLevel(log_level)

    # 检查 logger 是否已有处理器，避免重复添加
    # 重复添加会导致日志重复输出到文件或控制台，造成混乱
    if not logger.handlers:
        # 创建文件处理器，将日志写入指定文件
        # encoding 参数确保文件以指定编码（如 utf-8-sig）写入，适合 Windows 环境
        file_handler = logging.FileHandler(log_file, encoding=encoding)
        # 设置日志格式：时间 - 记录器名称 - 日志级别 - 消息
        # 例如：2025-05-22 16:04:05,123 - YOLO_Training - INFO - 日志初始化完成
        file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
        logger.addHandler(file_handler)

        # 创建控制台处理器，将日志输出到终端，便于实时查看
        console_handler = logging.StreamHandler()
        # 使用与文件相同的日志格式，确保文件和控制台输出一致
        console_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
        logger.addHandler(console_handler)

    # 记录日志元数据，便于调试和追踪日志配置
    # 包括日志目录、文件名（含临时或最终前缀）、编码、类型和级别
    logger.info(f"日志目录: {log_dir}")
    logger.info(f"日志文件: {log_file} (编码: {encoding})")
    logger.info(f"日志类型: {log_type}")
    logger.info(f"日志级别: {logging.getLevelName(log_level)}")
    logger.info("日志初始化完成")

    # 返回配置好的日志记录器，供调用者使用
    # 调用者可通过 logger.info()、logger.error() 等记录日志
    return logger


# 获得详细的训练设备信息
def get_device_info(device):
    """
    获取详细的训练设备信息，包括GPU、CPU、内存等。
    Args:
        device (str): 设备标识，如 "0"（GPU）、"cpu"，或多 GPU 如 "0,1"。
    Returns:
        list[str]: 包含设备信息的字符串列表。
    """
    logger = logging.getLogger("YOLO_Training")
    device_info = []

    # 基本系统信息
    device_info.append(f"操作系统: {platform.platform()}")
    device_info.append(f"系统版本: {platform.system()} {platform.version()}")
    device_info.append(f"CPU型号: {platform.processor() or '未知'}")
    device_info.append(f"CPU核心数: {os.cpu_count() or '未知'}")
    device_info.append(f"架构类型: {platform.machine()}")
    device_info.append(f"Python版本: {platform.python_version()}")
    device_info.append(f"Pytorch版本: {torch.__version__}")

    # 规范化 device 输入
    device = str(device).strip().lower()
    if not device:
        device_info.append("设备标识: 未指定，使用默认设备")
        device = "cpu"  # 默认 CPU

    # 处理设备信息
    if device == "cpu":
        device_info.append("设备类型: CPU")
        device_info.append("CUDA可用性: 不可用")
    elif torch.cuda.is_available():
        try:
            # 处理单 GPU 或多 GPU（如 "0" 或 "0,1"）
            if device.isdigit():
                device_idx = int(device)
                if device_idx >= torch.cuda.device_count():
                    raise ValueError(f"GPU索引 {device_idx} 超出可用设备数量 {torch.cuda.device_count()}")
                device_info.append(f"设备类型: GPU (索引 {device_idx})")
                device_info.append(f"GPU型号: {torch.cuda.get_device_name(device_idx)}")
                device_info.append(f"GPU显存: {torch.cuda.get_device_properties(device_idx).total_memory / (1024 ** 3):.2f} GB")
                device_info.append(f"CUDA算力: {torch.cuda.get_device_capability(device_idx)}")
            elif "," in device:
                device_indices = [int(idx) for idx in device.split(",") if idx.isdigit()]
                for idx in device_indices:
                    if idx >= torch.cuda.device_count():
                        raise ValueError(f"GPU索引 {idx} 超出可用设备数量 {torch.cuda.device_count()}")
                    device_info.append(f"GPU {idx} 型号: {torch.cuda.get_device_name(idx)}")
                    device_info.append(f"GPU {idx} 显存: {torch.cuda.get_device_properties(idx).total_memory / (1024 ** 3):.2f} GB")
                device_info.append(f"设备类型: 多 GPU (索引 {device})")
            else:
                raise ValueError(f"无效的设备标识: {device}")

            device_info.append(f"CUDA版本: {torch.version.cuda or '未知'}")
            device_info.append(f"GPU设备数量: {torch.cuda.device_count()}")
            device_info.append(f"CUDA可用性: 可用")
        except ValueError as e:
            logger.error(f"设备信息错误: {e}")
            device_info.append(f"设备信息错误: {e}")
            device_info.append(f"设备标识: {device} (已回退到 CPU)")
            device_info.append("设备类型: CPU")
        except Exception as e:
            logger.error(f"获取GPU信息失败: {e}", exc_info=True)
            device_info.append(f"获取GPU信息失败: {e}")
            device_info.append(f"设备标识: {device} (已回退到 CPU)")
            device_info.append("设备类型: CPU")
    else:
        device_info.append(f"设备标识: {device} (CUDA不可用，已回退到 CPU)")
        device_info.append("设备类型: CPU")
        device_info.append("CUDA可用性: 不可用")

    return device_info


# 加载yaml配置文件
def load_yaml_config(config_path: Path, config_type="train"):
    """
    加载YAML配置文件。

    Args:
        congig_path (str): 配置文件路径。
        config_type (str): 配置文件类型，如 train、infer。

    Returns:
        dict: 加载的YAML配置文件。
    """
    # 统一转换为 Path 对象
    config_path = Path(config_path)
    if not config_path.exists():
        logger.warning(f"配置文件 {config_path} 不存在，尝试生成默认配置")
        if config_type in ['train', 'val', 'infer']:
            try:
                logger.info(f"生成默认 {config_type} 配置文件")
                config_path.parent.mkdir(parents=True, exist_ok=True)
                generate_default_yaml(config_path, config_type=config_type)
            except Exception as e:
                logger.error(f"生成默认配置文件失败: {e}")
                raise FileNotFoundError(f"无法生成默认配置文件: {config_path}")
        else:
            logger.error(f"不支持的配置类型: {config_type}")
            raise ValueError(f"不支持的配置类型: {config_type}")

    try:
        with open(config_path, 'r', encoding='utf-8-sig') as f:
            config = yaml.safe_load(f) or {}
        logger.info(f"成功加载配置文件: {config_path}")
        return config
    except yaml.YAMLError as e:
        logger.error(f"解析 YAML 文件失败: {config_path}, 错误: {e}")
        raise
    except Exception as e:
        logger.error(f"加载配置文件失败: {e}")
        raise

#
# def generate_default_train_yaml(config_path):
#     """
#     生成默认的配置文件,包含所有Yolo的训练参数和注释
#     :param config_path: 配置文件的路径,如: configs/train.yaml
#     :return: None
#     """
#     logger = logging.getLogger("YOLO_Training")
#     config = DEFAULT_TRAIN_CONFIG
#     commented_config = COMMENTED_CONFIG
#
#     config_path.parent.mkdir(parents=True, exist_ok=True)
#     try:
#         with open(config_path, "w", encoding="utf-8-sig") as f:
#             f.write(commented_config)
#             logger.info(f"生成默认配置文件成功: {config_path}")
#     except Exception as e:
#         logger.error(f"生成默认配置文件失败: {config_path},原因: {e}")


def generate_default_yaml(config_path: Path, config_type: str = 'train'):
    """
    生成默认的.YAML配置文件。

    Args:
        config_path (Path): 配置文件保存路径。
        config_type (str): 配置类型，'train', 'val' 或 'infer'。
    """

    config_path = Path(config_path)
    config_path.parent.mkdir(parents=True, exist_ok=True)

    if config_type == 'train':
        config = COMMENTED_TRAIN_CONFIG
    elif config_type == 'val':
        config = COMMENTED_VAL_CONFIG
    elif config_type == 'infer':
        config = COMMENTED_INFER_CONFIG
    else:
        logger.error(f"不支持的配置类型：{config_type}")
        raise ValueError(f"不支持的配置类型：{config_type}")

    with open(config_path, 'w', encoding='utf-8-sig') as f:
        f.write(config)

    logger.info(f"生成默认 {config_type} 配置文件成功：{config_path}")


def generate_default_infer_yaml(config_path):
    pass


def copy_checkpoint_models(train_dir, model_filename, logger):
    """
    复制训练好的模型到指定的目录下
    :param train_dir: 训练模型的地址
    :param model_filename: 模型的名称
    :param logger:
    :return:
    """
    if not train_dir or not isinstance(train_dir, Path):
        logger.error("无效训练目录, 跳过模型复制")
        return
    # date_str = datetime.now().strftime("%Y%m%d%_%H%M")
    date_str = datetime.now().strftime("%Y%m%d-%H%M%S")
    model_name = model_filename.replace('.pt', '')
    train_suffix = train_dir.name
    CHECKPOINTS_DIR.mkdir(parents=True, exist_ok=True)
    if not os.access(CHECKPOINTS_DIR, os.W_OK):
        logger.error(f"检查点目录: {CHECKPOINTS_DIR}不可写, 跳过模型复制")
        raise OSError
    for model_type in ['best','last']:
        src_pat = train_dir / "weights" / f"{model_type}.pt"
        if src_pat.exists():
            # copy修复点
            checkpoint_name = f"{train_suffix}-{date_str}-{model_name}-{model_type}.pt"
            checkpoint_path = CHECKPOINTS_DIR / checkpoint_name
            shutil.copy(src_pat, checkpoint_path)
            logger.info(f"{model_type}.pt 已复制到: {checkpoint_path}")
        else:
            logger.warning(f"{model_type}.pt 未找到: {src_pat}")


def merge_configs(args, yaml_config, mode='train'):
    """
    合并命令行参数和 YAML 配置文件参数，根据模式选择有效参数。

    Args:
        args: 命令行参数 (argparse.Namespace)。
        yaml_config: YAML 配置文件参数 (dict)。
        mode: 运行模式 ('train', 'val', 'predict')。
    Returns:
        Tuple[Namespace, Namespace]: (yolo_args, project_args)
    """
    logger = logging.getLogger("YOLO_Training")

    # 选择有效参数集合
    if mode == 'train':
        valid_args = VALID_YOLO_TRAIN_ARGS
    elif mode == 'val':
        valid_args = VALID_YOLO_VAL_ARGS
    elif mode == 'infer':
        valid_args = VALID_YOLO_INFER_ARGS
    else:
        logger.error(f"无效模式: {mode}，支持 'train', 'val', 'infer'")
        raise ValueError(f"无效模式: {mode}")

    project_args = argparse.Namespace()
    yolo_args = argparse.Namespace()

    # 合并命令行参数（优先级最高）
    cmd_args = {k: v for k, v in vars(args).items() if k != 'extra_args' and v is not None}
    for key, value in cmd_args.items():
        # 修正 classes 解析
        if key == 'classes' and value == 'None':
            value = None
        setattr(project_args, key, value)
        if key in valid_args:
            setattr(yolo_args, key, value)

    # 处理 extra_args
    if hasattr(args, 'extra_args'):
        if len(args.extra_args) % 2 != 0:
            logger.error("额外参数格式错误，必须成对出现")
            raise ValueError("额外参数格式错误")
        for i in range(0, len(args.extra_args), 2):
            key = args.extra_args[i].lstrip('--')
            value = args.extra_args[i + 1]
            try:
                if value.replace('.', '', 1).isdigit():
                    value = float(value) if '.' in value else int(value)
                elif value.lower() in ('true', 'false'):
                    value = value.lower() == 'true'
                elif value == 'None':
                    value = None
            except ValueError:
                logger.warning(f"无法转换额外参数 {key} 的值 {value}")
            setattr(project_args, key, value)
            if key in valid_args:
                setattr(yolo_args, key, value)
            setattr(project_args, f"{key}_specified", True)

    # 合并 YAML 参数（仅当 CLI 未指定）
    for key, value in yaml_config.items():
        if not hasattr(project_args, key):
            # 修正 classes 解析
            if key == 'classes' and value == 'None':
                value = None
            setattr(project_args, key, value)
            if key in valid_args:
                setattr(yolo_args, key, value)
        else:
            if getattr(project_args, key) != value:
                logger.info(f"参数 {key} 被命令行覆盖: CLI={getattr(project_args, key)}, YAML={value}")

    # 标记参数来源
    for key in cmd_args:
        setattr(project_args, f"{key}_specified", True)
    if hasattr(args, 'extra_args'):
        for i in range(0, len(args.extra_args), 2):
            key = args.extra_args[i].lstrip('--')
            setattr(project_args, f"{key}_specified", True)
    for key in yaml_config:
        if not hasattr(project_args, f"{key}_specified"):
            setattr(project_args, f"{key}_specified", False)

    # 验证参数
    if mode == 'train':
        if hasattr(yolo_args, 'epochs') and (yolo_args.epochs <= 0):
            logger.error("训练轮数 (epochs) 必须为正整数")
            raise ValueError("训练轮数 (epochs) 必须为正整数")
        if hasattr(yolo_args, 'imgsz') and (yolo_args.imgsz <= 0 or yolo_args.imgsz % 32 != 0):
            logger.error("图像尺寸 (imgsz) 必须为正整数且为 32 的倍数")
            raise ValueError("图像尺寸 (imgsz) 必须为正整数且为 32 的倍数")
        if hasattr(yolo_args, 'batch') and yolo_args.batch is not None and yolo_args.batch <= 0:
            logger.error("批次大小 (batch) 必须为正整数")
            raise ValueError("批次大小 (batch) 必须为正整数")
    if mode == 'val' and not hasattr(yolo_args, 'split'):
        logger.warning("验证模式缺少 split 参数，设置为默认 'val'")
        setattr(yolo_args, 'split', 'val')

    # 标准化路径
    if hasattr(args, 'data') and args.data and not Path(args.data).is_absolute():
        data_path = YOLO_SERVICE_DIR / "configs" / args.data
        args.data = str(data_path)
        setattr(project_args, 'data', str(data_path))
        if 'data' in valid_args:
            setattr(yolo_args, 'data', str(data_path))
        logger.info(f"标准化数据集路径: {args.data}")

    # 调试：打印 yolo_args
    logger.debug(f"yolo_args: {vars(yolo_args)}")
    return yolo_args, project_args


def log_training_params(args, model_path, configs_dir, logger, mode="train"):
    """
    记录 YOLO 参数、设备信息和数据集信息。

    Args:
        args: 命令行参数。
        model_path: 模型文件路径。
        configs_dir: 配置文件目录。
        logger: 日志记录器。
        mode: 模式，'train' 或 'val' 。
    """

    logger.info(f"======== 此次 {mode} 过程中 YOLO 参数 ===========")
    logger.info(f"模型文件：{model_path}")

    for key, value in vars(args).items():
        if not key.endswith('_specified') and key not in ['log_encoding', 'use_cuda', 'log_level', 'extra_args']:
            source = '命令行' if getattr(args, f'{key}_specified', False) else 'YAML'
            logger.info(f"- {key}: {value} (来源:{source})")

    logger.info(f"======== 此次 {mode} 过程中 设备信息 ===========")
    for info in get_device_info(args.device):
        logger.info(info)

    if mode == "infer":
        logger.info(f"======== 推理过程中不记录数据集信息 ===========")
    else:
        logger.info(f"配置文件目录：{args.data}")
        logger.info(f"======== 此次 {mode} 过程中 数据集信息 ===========")
        nc, names, samples, sample_source = get_dataset_info(args.data, mode=mode)
        logger.info(f"数据集类别数：{nc}")
        logger.info(f"数据集类目名称：{','.join(names) if names else '未知'}")
        logger.info(f"数据集样本数：{samples} 来自于：{sample_source}")
        logger.info(f"==============================================")


def log_execution_time(logger_name="YOLO_Training"):
    """
    装饰器：记录函数执行时间并写入日志。

    Args:
        logger_name (str): 日志记录器的名称，默认为 YOLO_Training。

    Returns:
        callable: 装饰器函数。
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            logger = logging.getLogger(logger_name)
            start_time = time.time()
            logger.info(f"开始执行: {func.__name__}")
            result = func(*args, **kwargs)
            total_time = time.time() - start_time
            logger.info(f"{func.__name__} 完成，总耗时: {total_time:.2f} 秒 (约 {total_time / 60:.2f} 分钟)")
            return result
        return wrapper
    return decorator


def rename_log_file(logger, save_dir, model_name, encoding):
    """
    重命名日志文件，使用训练目录的名称（如 train, train2）。

    功能概述：
    - 将临时日志文件名（例如 temp_20250522_201258_yolov8n.log）重命名为基于训练目录的名称（例如 train2_20250522_201258_yolov8n.log）。
    - 确保日志文件名与 YOLOv8 训练输出目录（runs/detect/trainN）保持一致，便于实验追踪。
    - 更新日志记录器的文件处理器，确保重命名后日志继续写入新文件。
    - 主要用于解决训练开始时无法预知训练目录名（如 train2）的问题（例如在并发训练场景下）。

    参数说明：
    :param logger (logging.Logger): 日志记录器对象，通常为名称为 "YOLO_Training" 的 logger，包含文件和控制台处理器。
    :param save_dir (Path): 训练输出目录（Path 对象，例如 runs/detect/train2），由 YOLOv8 的 model.train 返回的 results.save_dir 提供。
    :param model_name (str): 模型名称（例如 yolov8n），用于构造新日志文件名。
    :param encoding (str): 日志文件编码（例如 'utf-8-sig'），用于创建新文件处理器，确保日志文件正确写入。

    处理逻辑：
    1. 遍历 logger 的处理器，找到文件处理器（FileHandler）。
    2. 从旧日志文件名中提取时间戳（例如 20250522_201258）。
    3. 使用训练目录名称（例如 train2）作为新文件名前缀。
    4. 构造新日志文件名（例如 train2_20250522_201258_yolov8n.log）。
    5. 关闭并移除旧文件处理器。
    6. 重命名日志文件（从旧文件名到新文件名）。
    7. 创建并添加新文件处理器，继续记录日志。
    8. 记录重命名操作到日志。

    注意事项：
    - 假设旧日志文件名格式为 {prefix}_{timestamp}_{model_name}.log（例如 temp_20250522_201258_yolov8n.log）。
    - 时间戳通过 split('_')[1] 提取，依赖文件名格式正确（至少包含两部分）。
    - 重命名操作需确保旧文件存在，且新文件名不冲突（通常由时间戳保证唯一性）。
    - 文件处理器关闭和重新创建是必要的，避免文件访问冲突。
    - 只处理第一个文件处理器，忽略其他处理器（例如控制台处理器）。
    """
    # 遍历 logger 的所有处理器，寻找文件处理器（FileHandler）
    # logger.handlers 可能包含多个处理器（如 FileHandler 和 StreamHandler）
    for handler in logger.handlers:
        # 检查当前处理器是否为 FileHandler（文件处理器）
        if isinstance(handler, logging.FileHandler):
            # 获取旧日志文件的完整路径（例如 C:\...\logging\train\temp_20250522_201258_yolov8n.log）
            # handler.baseFilename 是文件处理器的文件路径
            old_log_file = Path(handler.baseFilename)

            # 从旧文件名中提取时间戳（例如 20250522_201258）
            # 假设文件名格式为 {prefix}_{timestamp}_{model_name}.log，split('_') 分割后取第二部分
            # 例如：temp_20250522_201258_yolov8n.log -> ['temp', '20250522_201258', 'yolov8n']
            timestamp = old_log_file.stem.split('_')[1]

            # 获取训练目录的名称（例如 train, train2）
            # save_dir 是 Path 对象，.name 返回目录名（例如 runs/detect/train2 -> train2）
            train_prefix = Path(save_dir).name

            # 构造新日志文件名，格式为 {train_prefix}_{timestamp}_{model_name}.log
            # 例如：train2_20250522_201258_yolov8n.log
            # new_log_file 是 Path 对象，位于与旧文件相同的目录（old_log_file.parent）
            new_log_file = old_log_file.parent / f"{train_prefix}_{timestamp}_{model_name}.log"

            # 关闭当前文件处理器，释放文件资源
            # 必须关闭 handler，否则重命名操作可能因文件占用而失败（尤其在 Windows 上）
            handler.close()

            # 从 logger 中移除旧文件处理器
            # 移除后，日志不会继续写入旧文件
            logger.removeHandler(handler)

            # 检查旧日志文件是否存在，然后执行重命名
            # 重命名将旧文件（temp_20250522_201258_yolov8n.log）改为新文件（train2_20250522_201258_yolov8n.log）
            if old_log_file.exists():
                old_log_file.rename(new_log_file)
                # 记录重命名操作到日志，便于调试和追踪
                # 此时日志会写入控制台（StreamHandler），因为旧文件处理器已移除
                logger.info(f"日志文件重命名为: {new_log_file}")

            # 创建新文件处理器，指向新日志文件
            # 使用相同的编码（例如 utf-8-sig），确保新日志文件格式一致
            new_handler = logging.FileHandler(new_log_file, encoding=encoding)

            # 设置新处理器的日志格式，与旧处理器一致
            # 格式为：时间 - 记录器名称 - 日志级别 - 消息
            # 例如：2025-05-22 20:14:12,694 - YOLO_Training - INFO - 日志文件重命名为: ...
            new_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))

            # 将新文件处理器添加到 logger
            # 此后，日志将继续写入新文件（train2_20250522_201258_yolov8n.log）
            logger.addHandler(new_handler)

            # 找到第一个文件处理器后退出循环
            # 通常 logger 只有一个文件处理器，break 避免处理其他无关处理器
            break


if __name__ == '__main__':
    device_info = get_device_info("0")
    # for info in device_info:
    #     print(info)
